// 线程相关单元测试
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <thread>

#include "xlcomm/base/blocking_queue.h"
#include "xlcomm/base/bounded_blocking_queue.h"
#include "xlcomm/base/condition_latch.h"
#include "xlcomm/base/count_down_latch.h"
#include "xlcomm/base/demo_singleton.h"
#include "xlcomm/base/thread_pool.h"

#define BOOST_TEST_MAIN
#define BOOST_TEST_DYN_LINK
#include "boost/test/unit_test.hpp"

BOOST_AUTO_TEST_CASE(CountDownLatch) {
  std::cout << "-------------CountDownLatch\n";
  xlcomm::CountDownLatch latch(3);

  std::thread thread_wait1([&latch]() {
    latch.WaitZero();
    BOOST_CHECK(latch.count() == 0);
  });

  for (int i = 0; i < 3; ++i) {
    std::thread thread_work([&latch]() { latch.CountDown(); });
    thread_work.join();
  }

  std::thread thread_wait2([&latch]() {
    latch.WaitZero();
    BOOST_CHECK(latch.count() == 0);
  });

  thread_wait1.join();
  thread_wait2.join();
}

BOOST_AUTO_TEST_CASE(ConditionLatch) {
  std::cout << "-------------ConditionLatch\n";
  {
    xlcomm::ConditionLatch latch;
    std::thread thr1([&latch]() { latch.SetAndNotifyOnce(); });
    std::thread thr2([&latch]() { latch.Wait(); });
    thr1.join();
    thr2.join();
  }

  {
    xlcomm::ConditionLatch latch;
    std::thread thr2([&latch]() { latch.Wait(); });
    std::thread thr1([&latch]() { latch.SetAndNotifyOnce(); });
    thr1.join();
    thr2.join();
  }

  xlcomm::ConditionLatch latch2;
  latch2.SetAndNotifyOnce();
  latch2.Wait();
}

BOOST_AUTO_TEST_CASE(BlockingQueue) {
  std::cout << "-------------BlockingQueue\n";
  std::cout << "队列是个好东西\n";
  xlcomm::BlockingQueue<int> queue;

  std::thread thread_eat1([&queue]() {
    int n = queue.Take();
    std::cout << "Take:" << n << std::endl;
  });

  for (int i = 0; i < 3; ++i) {
    std::thread thread_feed([&queue, i]() { queue.Put(i); });
    thread_feed.join();
  }

  std::thread thread_eat2([&queue]() {
    int n = queue.Take();
    std::cout << "Take:" << n << std::endl;
  });

  thread_eat1.join();
  thread_eat2.join();

  BOOST_CHECK(queue.Size() == 1);

  xlcomm::BlockingQueue<int>::QueueType other = queue.Drain();
  std::cout << "other:" << other[0] << std::endl;
  BOOST_CHECK(other.size() == 1);
}

BOOST_AUTO_TEST_CASE(BoundedBlockingQueue) {
  std::cout << "-------------BoundedBlockingQueue\n";
  xlcomm::BoundedBlockingQueue<std::string> queue(2);

  BOOST_CHECK(queue.Empty());

  std::thread thread_eat1([&queue]() {
    std::string str = queue.Take();
    std::cout << "Take:" << str << std::endl;
  });

  for (int i = 0; i < 3; ++i) {
    std::thread thread_feed([&queue, i]() { queue.Put("hello"); });
    thread_feed.join();
  }
  BOOST_CHECK(queue.Full());
  std::thread thread_feed_wait([&queue]() {
    std::string str = "queue";
    queue.Put(std::move(str));
  });

  std::thread thread_eat2([&queue]() {
    std::string str = queue.Take();
    std::cout << "Take:" << str << std::endl;
    str = queue.Take();
    std::cout << "Take:" << str << std::endl;
    str = queue.Take();
    std::cout << "Take:" << str << std::endl;
  });

  thread_eat1.join();
  thread_eat2.join();
  thread_feed_wait.join();

  BOOST_CHECK(queue.Size() == 0);
  BOOST_CHECK(queue.Capacity() == 2);
}

BOOST_AUTO_TEST_CASE(DemoSingleton) {
  std::cout << "-------------DemoSingleton\n";
  // xlcomm::DemoSingleton* single1 = nullptr;
  // std::thread thread1(
  //     [&single1]() { single1 = xlcomm::DemoSingleton::Instance(); });
  // xlcomm::DemoSingleton* single2 = nullptr;
  // std::thread thread2(
  //     [&single2]() { single2 = xlcomm::DemoSingleton::Instance(); });

  // thread1.join();
  // thread2.join();

  // BOOST_CHECK(single1 == single2);

  xlcomm::DemoSingleton& single3 = xlcomm::DemoSingleton::Instance();
  xlcomm::DemoSingleton& single4 = xlcomm::DemoSingleton::Instance();
  BOOST_CHECK(&single3 == &single4);
}

void Task1() { std::cout << __FUNCTION__ << std::endl; }

void Task2(int count, int64_t* sum) {
  std::cout << "count:" << count;
  *sum = 0;
  for (int i = 0; i < count; ++i) {
    *sum += (i * 2);
  }
  std::cout << " sum:" << *sum << std::endl;
}

class TaskTest {
 public:
  void fun1(std::string str) {
    std::cout << __FUNCTION__ << " " << str << std::endl;
  }
};

void TaskException() {
  std::cout << __FUNCTION__ << std::endl;
  int a = 0;
  int b = 0;
  // b = 10 / a;
  std::cout << "a:" << a << ", b:" << b;
  int arr[2] = {0, 1};
  std::cout << " arr[1]:" << arr[1] << std::endl;
  // std::cout << " arr[2]:" << arr[2] << std::endl;
  // throw std::out_of_range("self");
}

// void TaskDump() {
//   char* p = new char[10];
//   delete[] p;
//   p = nullptr;
//   p[2] = 10;
// }

BOOST_AUTO_TEST_CASE(ThreadPool) {
  std::cout << "-------------ThreadPool\n";
  xlcomm::ThreadPool pool("test", 3);
  std::cout << "name:" << pool.name() << std::endl;
  pool.Start(2, []() {});

  std::thread thr1([&pool]() { pool.AddTask(Task1); });

  int64_t sum;
  std::thread thr2([&pool, &sum]() {
    pool.AddTask(std::bind(Task2, 100, &sum));  // 异步执行，没有同步等结果
    std::cout << "sum:" << sum << " !Async run!" << std::endl;
  });

  TaskTest task_test;
  pool.AddTask(std::bind(&TaskTest::fun1, &task_test, "bindstring"));

  pool.AddTask(TaskException);

  // while (pool.QueueSize() > 0) {
  //   std::this_thread::sleep_for(std::chrono::milliseconds(100));
  // }
  pool.Stop();

  thr1.join();
  thr2.join();
}

BOOST_AUTO_TEST_CASE(ThreadPoolTaskorder) {
  std::cout << "-------------ThreadPoolTaskorder\n";
  xlcomm::ThreadPool pool("task_order", 3);
  pool.Start(1);  // 一个工作线程才能看出队列顺序

  std::cout << "AddTask order:100 200 300...\n";
  int64_t sum;
  pool.AddTask(std::bind(Task2, 100, &sum));
  pool.AddTask(std::bind(Task2, 200, &sum));
  pool.AddTask(std::bind(Task2, 300, &sum));

  while (pool.QueueSize() > 0) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }
}

struct TaskStruct {
  int data;
};

class TaskEnd {
 public:
  ~TaskEnd() {
    std::cout << "this:" << this << " " << __FUNCTION__ << std::endl;
  }
  void fun1(const TaskStruct& ts) {
    std::cout << "this:" << this << " " << __FUNCTION__ << " num_:" << num_
              << ", ts.data:" << ts.data << ", &(ts.data):" << &(ts.data)
              << std::endl;
  }
  void set_num(int num) { num_ = num; }

 private:
  int num_;
};

BOOST_AUTO_TEST_CASE(ThreadPoolEndlife) {
  std::cout << "-------------ThreadPoolEndlife\n";
  xlcomm::ThreadPool pool("endlife", 10);
  pool.Start(1);

  {
    TaskEnd task_end;
    task_end.set_num(1);
    TaskStruct ts;
    ts.data = 11;
    std::cout << "&(ts.data):" << &(ts.data) << std::endl;
    pool.AddTask(std::bind(&TaskEnd::fun1, &task_end, ts));
  }
  {
    TaskEnd task_end;
    task_end.set_num(2);
    TaskStruct ts;
    ts.data = 22;
    std::cout << "&(ts.data):" << &(ts.data) << std::endl;
    pool.AddTask(std::bind(&TaskEnd::fun1, &task_end, ts));
    // 裸指针类型参数有问题，引用类型参数没问题(std::bind做了拷贝)
  }

  {
    TaskEnd task_end;
    task_end.set_num(3);
    TaskStruct ts;
    ts.data = 33;
    std::cout << "&(ts.data):" << &(ts.data) << std::endl;
    pool.AddTask(std::bind(&TaskEnd::fun1, &task_end, std::ref(ts)));
  }
  {
    TaskEnd task_end;
    task_end.set_num(4);
    TaskStruct ts;
    ts.data = 44;
    std::cout << "&(ts.data):" << &(ts.data) << std::endl;
    pool.AddTask(std::bind(&TaskEnd::fun1, &task_end, std::ref(ts)));
    // std::ref引用类型参数有问题
  }

  {
    auto task_end = std::make_shared<TaskEnd>();
    task_end->set_num(5);
    TaskStruct ts;
    ts.data = 55;
    pool.AddTask(std::bind(&TaskEnd::fun1, task_end, ts));
  }
  {
    auto task_end = std::make_shared<TaskEnd>();
    task_end->set_num(6);
    TaskStruct ts;
    ts.data = 66;
    pool.AddTask(std::bind(&TaskEnd::fun1, task_end, ts));
    // 智能指针类型参数没问题。可以在std::bind里传智能指针(std::function保存一份引用,保证不被析构)

    // std::bind knows how to directly bind a member function to a shared_ptr
    // https://stackoverflow.com/questions/13272831/how-to-combine-the-use-of-stdbind-with-stdshared-ptr

    // shared_ptr will be copied to some object(Task) returned from std::bind
    // https://stackoverflow.com/questions/20609209/stdfunction-and-shared-ptr
  }

  while (pool.QueueSize() > 0) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }
}

struct UserParam1 {
  int a;
  int b;
};
struct UserResult1 {
  int sum;
  int sub;
};
using Callback = void(const UserResult1& result);

void TaskResult1(const UserParam1& param, Callback callback) {
  UserResult1 result;
  result.sum = param.a + param.b;
  result.sub = param.a - param.b;
  callback(result);
}

BOOST_AUTO_TEST_CASE(ThreadPoolResult) {
  std::cout << "-------------ThreadPoolResult\n";
  xlcomm::ThreadPool pool("result", 10);
  pool.Start(2);

  UserParam1 param{20, 10};
  auto cb = [](const UserResult1& result) {
    BOOST_ASSERT(result.sum == 30);
    BOOST_ASSERT(result.sub == 10);
  };
  pool.AddTask(std::bind(TaskResult1, param, cb));  // 执行完回调获取结果

  while (pool.QueueSize() > 0) {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }
}